﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using PostSharp.Aspects;

namespace Theoreme.Teaching.AOP.Sample01
{
    // @Section(PerformanceCounterAttribute)

    /// <summary>
    /// Base class for all performance counter aspects.
    /// </summary>
    /// <remarks>This class takes care of the identification and initialization of the
    /// performance counter. Derived class may access the performance counter at runtime 
    /// on the <see cref="PerformanceCounter"/> property.</remarks>
    [Serializable]
    public abstract class PerformanceCounterAttribute : OnMethodBoundaryAspect
    {
        // Serialized fields: set at build time, used at run time.
        private readonly string categoryName;
        private readonly string counterName;
        private readonly string baseCounterName;

        private readonly PerformanceCounterType counterType;
        private readonly PerformanceCounterType baseCounterType;

        // Not serialized because used at rutime only.
        [NonSerialized] private PerformanceCounter performanceCounter, basePerformanceCounter;

        // Static field containing data that are shared among all aspects of an assembly and must be
        // serialized in the assembly.
        private static readonly SharedData sharedData = new SharedData();

        // We need a reference to shared data as an instance field to make sure they are serialized.
        private readonly SharedData sharedDataRef;

        protected PerformanceCounterAttribute( string categoryName, string counterName, PerformanceCounterType counterType )
            : this( categoryName, counterName, counterType, (PerformanceCounterType) (-1) )
        {
        }

        protected PerformanceCounterAttribute( string categoryName, string counterName, PerformanceCounterType counterType,
                                               PerformanceCounterType baseCounterType )
        {
            this.categoryName = categoryName;
            this.baseCounterType = baseCounterType;
            this.counterType = counterType;
            this.counterName = counterName;
            this.baseCounterName = counterName + "Base";
            this.CategoryType = PerformanceCounterCategoryType.MultiInstance;
            this.sharedDataRef = sharedData;
        }

        /// <summary>
        /// Gets the performance counter.
        /// </summary>
        protected PerformanceCounter PerformanceCounter
        {
            get { return this.performanceCounter; }
        }

        protected PerformanceCounter BasePerformanceCounter
        {
            get { return this.basePerformanceCounter; }
        }

        public PerformanceCounterCategoryType CategoryType { get; set; }

        public override void CompileTimeInitialize( MethodBase method, AspectInfo aspectInfo )
        {
            PerformanceCounterCategoryData categoryData;

            if ( !sharedDataRef.Categories.TryGetValue( this.categoryName, out categoryData ) )
            {
                categoryData = new PerformanceCounterCategoryData {CategoryType = this.CategoryType};
                sharedDataRef.Categories.Add( this.categoryName, categoryData );
            }
            else if ( categoryData.CategoryType != this.CategoryType )
            {
                // TODO: Emit an error.
            }

            categoryData.Counters.Add( new CounterCreationData( this.counterName, "", this.counterType ) );
            if ( this.baseCounterType >= 0 )
                categoryData.Counters.Add( new CounterCreationData( this.baseCounterName, "", this.baseCounterType ) );
        }

        private void InitializeCounters()
        {
            // Create the performance counters. We do this operation once for every assembly, so
            // we can create categories that contain several counters.
            switch ( this.sharedDataRef.Status )
            {
                case PerformanceCounterCategoryStatus.Uninitialized:
                    try
                    {
                        foreach ( KeyValuePair<string, PerformanceCounterCategoryData> pair in this.sharedDataRef.Categories )
                        {
                            if ( !PerformanceCounterCategory.Exists( pair.Key ) )
                            {
                                Trace.TraceInformation( "Creating performance counters for category '{0}'.", pair.Key );
                                PerformanceCounterCategory.Create( pair.Key, "", pair.Value.CategoryType, pair.Value.Counters );
                            }
                            else
                            {
                                PerformanceCounterCategory category = new PerformanceCounterCategory( pair.Key );
                                bool invalid = false;

                                if ( category.CategoryType != pair.Value.CategoryType )
                                {
                                    invalid = true;
                                }

                                foreach ( CounterCreationData counterData in pair.Value.Counters )
                                {
                                    if ( !category.CounterExists( counterData.CounterName ) )
                                    {
                                        invalid = true;
                                        break;
                                    }

                                    PerformanceCounter counter = CreatePerformanceCounter( pair.Key, counterData.CounterName, pair.Value.CategoryType, true );
                                    if ( counter.CounterType != counterData.CounterType )
                                    {
                                        invalid = true;
                                        break;
                                    }
                                }

                                if ( invalid )
                                {
                                    Trace.TraceInformation("Recreating performance counters for category '{0}'.", pair.Key);
                                    PerformanceCounterCategory.Delete( pair.Key );
                                    PerformanceCounterCategory.Create( pair.Key, "", pair.Value.CategoryType, pair.Value.Counters );
                                }
                            }
                        }
                    }
                    catch ( Exception e )
                    {
                        Trace.TraceError( "Cannot initialize performance counter {0}.{1}: {2}",
                                          this.categoryName, this.counterName, e.Message );
                        this.sharedDataRef.Status = PerformanceCounterCategoryStatus.Failed;
                        return;
                    }
                    this.sharedDataRef.Status = PerformanceCounterCategoryStatus.Initialized;
                    break;

                case PerformanceCounterCategoryStatus.Failed:
                    return;
            }
        }

        /// <summary>
        /// Method executed at run time just after the aspect is deserialized.
        /// </summary>
        /// <param name="method">>Method to which the current aspect instance 
        /// has been applied.</param>
        public override void RuntimeInitialize( MethodBase method )
        {
            this.InitializeCounters();

            this.performanceCounter = CreatePerformanceCounter( this.categoryName, this.counterName, this.CategoryType, false );

            if ( this.baseCounterType >= 0 )
            {
                this.basePerformanceCounter = CreatePerformanceCounter( this.categoryName, this.baseCounterName, this.CategoryType, false );
            }
        }

        private static PerformanceCounter CreatePerformanceCounter( string categoryName, string counterName, PerformanceCounterCategoryType categoryType,
                                                                    bool isReadonly )
        {
            if ( categoryType == PerformanceCounterCategoryType.MultiInstance )
            {
                return new PerformanceCounter( categoryName, counterName, PerformanceCounterSettings.InstanceName, isReadonly );
            }
            else
            {
                return new PerformanceCounter( categoryName, counterName, isReadonly );
            }
        }

        #region Nested types

        [Serializable]
        private class SharedData
        {
            public PerformanceCounterCategoryStatus Status { get; set; }
            private readonly Dictionary<string, PerformanceCounterCategoryData> categories = new Dictionary<string, PerformanceCounterCategoryData>();

            public Dictionary<string, PerformanceCounterCategoryData> Categories
            {
                get { return categories; }
            }
        }

        [Serializable]
        private class PerformanceCounterCategoryData
        {
            public PerformanceCounterCategoryData()
            {
                this.Counters = new CounterCreationDataCollection();
            }

            public PerformanceCounterCategoryType CategoryType { get; set; }
            public CounterCreationDataCollection Counters { get; private set; }
        }

        private enum PerformanceCounterCategoryStatus
        {
            Uninitialized,
            Initialized,
            Failed
        }

        #endregion
    }

    // @Section()
}